﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Ochkin.SquaredSoccer.Base;
using System.Diagnostics;

namespace Ochkin.SquaredSoccer.WPFTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.controller = new GameController(this);
        }

        private readonly GameController controller;

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.controller.StartGame();
        }

        private void gameField_KeyUp(object sender, KeyEventArgs e)
        {
            this.controller.MakeStep(e.Key);
        }
    }

    public sealed class GameController
    {
        private readonly MainWindow window;
        private readonly Ellipse ball;
        private Match game;

        public GameController(MainWindow source)
        {
            this.window = source;

            this.ball = new Ellipse();
            this.ball.Width = BALL_SIZE;
            this.ball.Height = BALL_SIZE;
            this.ball.Fill = new SolidColorBrush(Color.FromArgb(255, 255, 255, 0));
            this.ball.StrokeThickness = 2;
            this.ball.Stroke = Brushes.Black;
            this.ball.HorizontalAlignment = HorizontalAlignment.Left;
            this.ball.VerticalAlignment = VerticalAlignment.Top;
            //this.ball.SetValue(Canvas.LeftProperty, FieldCenter.X);
            //this.ball.SetValue(Canvas.TopProperty, FieldCenter.Y);
            //this.ball.RenderTransform.

        }

        private const int GAME_WIDTH = 5;
        private const int GAME_HEIGHT = 3;
        private const int BALL_SIZE = 10;
        private double FieldWidth
        {
            get
            {
                return this.window.gameField.ActualWidth;
            }
        }
        private double FieldHeight
        {
            get
            {
                return this.window.gameField.ActualHeight;
            }
        }
        private Point FieldCenter
        {
            get
            {
                return new Point(FieldWidth/2, FieldHeight/2);
            }
        }

        private double GetX(Spot point)
        {
            return FieldCenter.X + (FieldWidth / (2 * GAME_WIDTH)) * point.X;
        }

        private double GetY(Spot point)
        {
            return FieldCenter.Y - (FieldHeight / (2 * GAME_HEIGHT)) * point.Y;
        }

        //private struct GamePoint
        //{
        //    public readonly int X;
        //    public readonly int Y;
        //    public GamePoint(int x, int y)
        //    {
        //        this.X = x;
        //        this.Y = y;
        //    }
        //}

        public void StartGame()
        {
            var leftUpperCorner = new Spot(-GAME_WIDTH, GAME_HEIGHT);
            var rightUpperCorner = new Spot(GAME_WIDTH, GAME_HEIGHT);
            var leftLowerCorner = new Spot(-GAME_WIDTH, -GAME_HEIGHT);
            var rightLowerCorner = new Spot(GAME_WIDTH, -GAME_HEIGHT);
            DrawLine(leftLowerCorner, leftUpperCorner);
            DrawLine(leftUpperCorner, rightUpperCorner);
            DrawLine(rightLowerCorner, rightUpperCorner);
            DrawLine(leftLowerCorner, rightLowerCorner);

            var middleLineL = new Spot(-GAME_WIDTH, 0);
            var middleLineR = new Spot(GAME_WIDTH, 0);
            DrawLine(middleLineL, middleLineR);

            var goalLeftUpper = new Spot(-1, -GAME_HEIGHT);
            var goalRightUpper = new Spot(1, -GAME_HEIGHT);
            DrawGoal(goalLeftUpper, goalRightUpper);

            var goalLeftLower = new Spot(-1, GAME_HEIGHT);
            var goalRightLower = new Spot(1, GAME_HEIGHT);
            DrawGoal(goalLeftLower, goalRightLower);

            this.window.gameField.Children.Add(this.ball);
            SetBall(FieldCenter);

            this.game = new Match(GAME_WIDTH, GAME_HEIGHT, false, GameStyle.Random);
        }

        private void DrawLine(Spot begin, Spot end)
        {
            ValidateSpot(begin, end);
            Line line = new Line();
            line.Stroke = Brushes.LightSteelBlue;
            line.X1 = GetX(begin);
            line.Y1 = GetY(begin);
            line.X2 = GetX(end);
            line.Y2 = GetY(end);
            line.HorizontalAlignment = HorizontalAlignment.Stretch;
            line.VerticalAlignment = VerticalAlignment.Stretch;
            line.StrokeThickness = 2;
            this.window.gameField.Children.Add(line);
        }

        private void DrawGoal(Spot begin, Spot end)
        {
            ValidateSpot(begin, end);
            Line line = new Line();
            line.Stroke = Brushes.DarkSlateBlue;
            line.X1 = GetX(begin);
            line.Y1 = GetY(begin);
            line.X2 = GetX(end);
            line.Y2 = GetY(end);
            line.HorizontalAlignment = HorizontalAlignment.Stretch;
            line.VerticalAlignment = VerticalAlignment.Stretch;
            line.StrokeThickness = 5;
            this.window.gameField.Children.Add(line);
        }

        private void SetBall(Point position)
        {
            //this.window.gameField.Child = this.ball;
            //this.ball.SetValue(Canvas.LeftProperty, FieldCenter.X);
            //this.ball.SetValue(Canvas.TopProperty, FieldCenter.Y);
            // - this.ball.RenderTransform.SetValue(Canvas.LeftProperty, FieldCenter.X);
            Canvas.SetTop(this.ball, position.Y - (BALL_SIZE / 2));
            Canvas.SetLeft(this.ball, position.X - (BALL_SIZE / 2));
        }

        private static Direction KeyToDirection(Key key)
        {
            switch (key)
            {
                case Key.D8:
                case Key.NumPad8:
                    return Direction.NewSimple(SideOfEarth.North);
                case Key.D9:
                case Key.NumPad9:
                    return Direction.NewMix(SideOfEarth.North, SideOfEarth.East);
                case Key.D6:
                case Key.NumPad6:
                    return Direction.NewSimple(SideOfEarth.East);
                case Key.D3:
                case Key.NumPad3:
                    return Direction.NewMix(SideOfEarth.South, SideOfEarth.East);
                case Key.D2:
                case Key.NumPad2:
                    return Direction.NewSimple(SideOfEarth.South);
                case Key.D1:
                case Key.NumPad1:
                    return Direction.NewMix(SideOfEarth.South, SideOfEarth.West);
                case Key.D4:
                case Key.NumPad4:
                    return Direction.NewSimple(SideOfEarth.West);
                case Key.D7:
                case Key.NumPad7:
                    return Direction.NewMix(SideOfEarth.North, SideOfEarth.West);
                default:
                    return null;
            }
        }

        public void MakeStep(Key key)
        {
            if (this.game.GameState != GameState.Default)
            {
                MessageBox.Show("You cannot move anymore. The game is over.");
                return;
            }

            if (this.game.WhoseTurn == Player.Computer)
            {
                MessageBox.Show("Be patient. It is computer's turn.");
                return;
            }

            Direction dir = KeyToDirection(key);
            if (dir == null)
            {
                MessageBox.Show("Invalid key. Please press a key on NumPad to specify direction.");
                return;
            }

            Spot initialPosition = this.game.CurrentPosition;
            bool success = this.game.MakeHumanMovePartial(dir);
            if (success)
            {
                this.DrawHumanStep(initialPosition, initialPosition.Shift(dir));
                if (this.game.GameState.Equals(GameState.Default))
                {
                    if (this.game.WhoseTurn.Equals(Player.Computer))
                    {
                        initialPosition = this.game.CurrentPosition;
                        var computerMove = this.game.GetComputerMove();
                        Debug.Assert(computerMove != null && Microsoft.FSharp.Core.FSharpOption<Microsoft.FSharp.Collections.FSharpList<Direction>>.get_IsSome(computerMove), "Invalid computer move.");
                        foreach (var step in computerMove.Value)
                        {
                            this.DrawComputerStep(initialPosition, initialPosition.Shift(step));
                            initialPosition = initialPosition.Shift(step);
                        }

                        if (!(this.game.GameState.Equals(GameState.Default)))
                            GameOver();
                    }
                }
                else
                {
                    GameOver();
                }
            }
        }

        private void GameOver()
        {
            string msg = (this.game.GameState.Equals(GameState.Tie) ? "Tie" : "") +
                (this.game.GameState.Equals(GameState.NewWon(Player.Computer)) ? "Computer won :-(" : "") +
                (this.game.GameState.Equals(GameState.NewWon(Player.Human)) ? "You won :-)" : "");
            MessageBox.Show(msg, "Game over", MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK, MessageBoxOptions.None);
            this.window.Close();
        }

        private void DrawHumanStep(Spot source, Spot target)
        {
            ValidateSpot(source, target);
            Line line = new Line();
            line.Stroke = Brushes.PaleVioletRed;
            line.X1 = GetX(source);
            line.Y1 = GetY(source);
            line.X2 = GetX(target);
            line.Y2 = GetY(target);
            line.HorizontalAlignment = HorizontalAlignment.Stretch;
            line.VerticalAlignment = VerticalAlignment.Stretch;
            line.StrokeThickness = 1;
            this.window.gameField.Children.Add(line);

            this.SetBall(new Point(GetX(target), GetY(target)));
        }
        private void DrawComputerStep(Spot source, Spot target)
        {
            ValidateSpot(source, target);
            Line line = new Line();
            line.Stroke = Brushes.PaleTurquoise;
            line.X1 = GetX(source);
            line.Y1 = GetY(source);
            line.X2 = GetX(target);
            line.Y2 = GetY(target);
            line.HorizontalAlignment = HorizontalAlignment.Stretch;
            line.VerticalAlignment = VerticalAlignment.Stretch;
            line.StrokeThickness = 1;
            this.window.gameField.Children.Add(line);

            this.SetBall(new Point(GetX(target), GetY(target)));
        }

        private static void ValidateSpot(params Spot[] spots)
        {
            foreach (Spot spot in spots)
                Debug.Assert(-GAME_WIDTH <= spot.X && spot.X <= GAME_WIDTH && -GAME_HEIGHT <= spot.Y && spot.Y <= GAME_HEIGHT, "Invalid spot.");
        }
    }
}
